package route

import (
	"net/http"
	"net/url"
	"path/filepath"
	"regexp"
	"strings"
)

const (
	DELETE = "DELETE"
	GET    = "GET"
	POST   = "POST"
	PUT    = "PUT"
)

type route struct {
	method  string
	regex   *regexp.Regexp
	params  map[int]string
	handler http.HandlerFunc
}

type RouteMux struct {
	routes  []*route
	filters []http.HandlerFunc
}

func New() *RouteMux {
	return &RouteMux{}
}

// Get adds a new Route for GET requests.
func (m *RouteMux) Get(pattern string, handler http.HandlerFunc) {
	m.AddRoute(GET, pattern, handler)
}

// Put adds a new Route for PUT requests.
func (m *RouteMux) Put(pattern string, handler http.HandlerFunc) {
	m.AddRoute(PUT, pattern, handler)
}

// Del adds a new Route for DELETE requests.
func (m *RouteMux) Del(pattern string, handler http.HandlerFunc) {
	m.AddRoute(DELETE, pattern, handler)
}

// Post adds a new Route for POST requests.
func (m *RouteMux) Post(pattern string, handler http.HandlerFunc) {
	m.AddRoute(POST, pattern, handler)
}

// Adds a new Route for Static http requests.
// Serves static files from the specified directory
func (m *RouteMux) Static(pattern string, dir string) {
	pattern = pattern + "(.+)"
	m.AddRoute(GET, pattern, func(w http.ResponseWriter, r *http.Request) {
		path := filepath.Clean(r.URL.Path)
		path = filepath.Join(dir, path)
		http.ServeFile(w, r, path)
	})
}

// Filter adds the middleware filter.
func (m *RouteMux) Filter(filter http.HandlerFunc) {
	m.filters = append(m.filters, filter)
}

// FilterParam adds the middleware filter iff the REST URL parameter exists.
func (m *RouteMux) FilterParam(param string, filter http.HandlerFunc) {
	if !strings.HasPrefix(param, ":") {
		param = ":" + param
	}

	m.Filter(func(w http.ResponseWriter, r *http.Request) {
		p := r.URL.Query().Get(param)
		if len(p) > 0 {
			filter(w, r)
		}
	})
}

// Adds a new Route to the Handler
func (m *RouteMux) AddRoute(method string, pattern string, handler http.HandlerFunc) {
	//split the url into sections
	parts := strings.Split(pattern, "/")

	//find params that start with ":" replace with regular expressions
	j := 0
	params := make(map[int]string)
	for i, part := range parts {
		if strings.HasPrefix(part, ":") {
			expr := "([^/]+)"

			if index := strings.Index(part, "("); index != -1 {
				expr = part[index:]
				part = part[:index]
			}
			params[j] = part
			parts[i] = expr
			j++
		}
	}

	pattern = strings.Join(parts, "/")
	regex, regexErr := regexp.Compile(pattern)
	if regexErr != nil {
		//TODO add error handling here to avoid panic
		panic(regexErr)
		return
	}

	//now create the Route
	route := &route{}
	route.method = method
	route.regex = regex
	route.handler = handler
	route.params = params

	//and finally append to the list of Routes
	m.routes = append(m.routes, route)
}

// Required by http.Handler interface.
// This method is invoked by the http server and will handle all page routing.
func (m *RouteMux) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	requestPath := r.URL.Path

	//wrap the response writer, in our custom interface
	w := &responseWriter{writer: rw}

	//find a matching Route
	for _, route := range m.routes {
		if r.Method != route.method {
			continue
		}
		if !route.regex.MatchString(requestPath) {
			continue
		}

		matches := route.regex.FindStringSubmatch(requestPath)

		if len(matches[0]) != len(requestPath) {
			continue
		}

		if len(route.params) > 0 {
			values := r.URL.Query()
			for i, match := range matches[1:] {
				values.Add(route.params[i], match)
			}

			r.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery
		}

		//execute middleware filters
		for _, filter := range m.filters {
			filter(w, r)
			if w.started {
				return
			}
		}

		//Invoke the request handler
		route.handler(w, r)
		break
	}

	//if no matches to url, throw a not found exception
	if w.started == false {
		http.NotFound(w, r)
	}
}


// responseWriter is a wrapper for the http.ResponseWriter to track if response was written to.
type responseWriter struct {
	writer  http.ResponseWriter
	started bool
	status  int
}

// Header returns the header map that will be sent by WriteHeader.
func (w *responseWriter) Header() http.Header {
	return w.writer.Header()
}

// Write writes the data to the connection as part of an HTTP reply, and sets `started` to true
func (w *responseWriter) Write(p []byte) (int, error) {
	w.started = true
	return w.writer.Write(p)
}

// WriteHeader sends an HTTP response header with status code, and sets `started` to true
func (w *responseWriter) WriteHeader(code int) {
	w.status = code
	w.started = true
	w.writer.WriteHeader(code)
}